<template>
  <div class="tutorial-page">
    <div class="tutorial-header">
      <h1>计算属性和侦听器</h1>
      <p>掌握计算属性和侦听器的使用场景和区别</p>
    </div>

    <div class="tutorial-content">
      <div class="section">
        <h2>计算属性 (Computed Properties)</h2>
        <p>模板内的表达式非常便利，但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。对于任何复杂逻辑，你都应当使用计算属性。</p>
        
        <h3>基础示例</h3>
        <pre><code class="language-javascript">export default {
  data() {
    return {
      message: 'Hello'
    }
  },
  computed: {
    // 计算属性的 getter
    reversedMessage() {
      return this.message.split('').reverse().join('')
    }
  }
}</code></pre>
        <pre><code class="language-html">&lt;p&gt;Original message: "{{ message }}"&lt;/p&gt;
&lt;p&gt;Computed reversed message: "{{ reversedMessage }}"&lt;/p&gt;</code></pre>
        
        <h3>计算属性缓存 vs 方法</h3>
        <p>我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而，不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。</p>
        <pre><code class="language-javascript">// 计算属性
computed: {
  now() {
    return Date.now()
  }
}

// 方法
methods: {
  now() {
    return Date.now()
  }
}</code></pre>
      </div>

      <div class="section">
        <h2>计算属性的 getter 和 setter</h2>
        <p>计算属性默认只有 getter，不过在需要时你也可以提供一个 setter：</p>
        
        <pre><code class="language-javascript">export default {
  data() {
    return {
      firstName: 'Foo',
      lastName: 'Bar'
    }
  },
  computed: {
    fullName: {
      // getter
      get() {
        return this.firstName + ' ' + this.lastName
      },
      // setter
      set(newValue) {
        const names = newValue.split(' ')
        this.firstName = names[0]
        this.lastName = names[names.length - 1]
      }
    }
  }
}</code></pre>
      </div>

      <div class="section">
        <h2>侦听器 (Watchers)</h2>
        <p>虽然计算属性在大多数情况下更合适，但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法，来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时，这个方式是最有用的。</p>
        
        <h3>基础用法</h3>
        <pre><code class="language-javascript">export default {
  data() {
    return {
      question: '',
      answer: 'Questions usually contain a question mark. ;-)'
    }
  },
  watch: {
    // 如果 `question` 发生改变，这个函数就会运行
    question(newVal, oldVal) {
      if (newVal.indexOf('?') > -1) {
        this.getAnswer()
      }
    }
  },
  methods: {
    async getAnswer() {
      this.answer = 'Thinking...'
      try {
        const res = await fetch('https://yesno.wtf/api')
        this.answer = (await res.json()).answer
      } catch (error) {
        this.answer = 'Error! Could not reach the API. ' + error
      }
    }
  }
}</code></pre>
        
        <h3>深度侦听器</h3>
        <pre><code class="language-javascript">export default {
  data() {
    return {
      obj: {
        a: 1,
        b: 2
      }
    }
  },
  watch: {
    obj: {
      handler(newVal, oldVal) {
        // 注意：在嵌套的变更中，只要没有替换对象本身，
        // 那么这里的 `newVal` 和 `oldVal` 是相等的
        // 因为它们都指向同一个对象！
      },
      deep: true
    }
  }
}</code></pre>
        
        <h3>立即执行的侦听器</h3>
        <pre><code class="language-javascript">export default {
  watch: {
    question: {
      handler(newVal, oldVal) {
        // ...
      },
      immediate: true
    }
  }
}</code></pre>
      </div>

      <div class="section">
        <h2>计算属性 vs 侦听器</h2>
        
        <h3>使用计算属性的场景</h3>
        <ul>
          <li>需要基于现有数据进行简单计算</li>
          <li>希望利用缓存机制提高性能</li>
          <li>数据展示相关的逻辑</li>
        </ul>
        
        <h3>使用侦听器的场景</h3>
        <ul>
          <li>需要在数据变化时执行异步操作</li>
          <li>需要执行开销较大的操作</li>
          <li>需要在数据变化时执行副作用</li>
          <li>需要监听对象或数组的深度变化</li>
        </ul>
        
        <h3>示例对比</h3>
        <pre><code class="language-javascript">// 使用计算属性
export default {
  data() {
    return {
      items: [
        { name: 'Apple', price: 1.2 },
        { name: 'Banana', price: 0.8 }
      ]
    }
  },
  computed: {
    totalPrice() {
      return this.items.reduce((sum, item) => sum + item.price, 0)
    }
  }
}

// 使用侦听器
export default {
  data() {
    return {
      question: '',
      answer: ''
    }
  },
  watch: {
    question: {
      handler(newVal) {
        // 执行异步操作
        this.searchAnswer(newVal)
      },
      immediate: true
    }
  },
  methods: {
    async searchAnswer(question) {
      // 模拟 API 调用
      const response = await fetch(`/api/search?q=${question}`)
      this.answer = await response.text()
    }
  }
}</code></pre>
      </div>

      <div class="section">
        <h2>最佳实践</h2>
        <ul>
          <li>优先使用计算属性而不是方法，因为计算属性具有缓存机制</li>
          <li>避免在计算属性中执行异步操作或修改数据</li>
          <li>合理使用侦听器的 deep 和 immediate 选项</li>
          <li>在组件销毁时清理侦听器中的定时器或事件监听器</li>
        </ul>
      </div>
    </div>
  </div>
</template>

<script setup>
// 页面逻辑可以在这里添加
</script>

<style scoped>
@import '@/assets/tutorial-styles.css';
</style>